/*
 * Copyright (c) 2022, IMMORTA Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice, this list
 *   of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright notice, this
 *   list of conditions and the following disclaimer in the documentation and/or
 *   other materials provided with the distribution.
 *
 * - Neither the name of IMMORTA Inc. nor the names of its
 *   contributors may be used to endorse or promote products derived from this
 *   software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef CLOCK_DRV_H
#define CLOCK_DRV_H

/*!
 * @file clock_drv.h
 * @brief This file declares clock driver interfaces
 */

/*!
 * @addtogroup clock_drv
 * @{
 */

/*******Includes***************************************************************/
#include <stdbool.h>
#include "errcodes.h"
#include "device_registers.h"

/*******Definitions************************************************************/

/*!
 * @brief PLL reference clock source
 */
typedef enum {
    PLL_REF_CLK_SRC_HSI = 0U, /*!< PLL reference clock source is HSI */
    PLL_REF_CLK_SRC_HSE = 1U  /*!< PLL reference clock source is HSE */
} pll_ref_clk_src_t;

/*!
 * @brief System clock source
 */
typedef enum {
    SYS_CLK_SRC_HSI = 0U, /*!< System clock source is HSI */
    SYS_CLK_SRC_HSE = 1U, /*!< System clock source is HSE */
    SYS_CLK_SRC_PLL = 2U  /*!< System clock source is PLL */
} sys_clk_src_t;

/*!
 * @brief ADC clock source
 */
typedef enum {
    ADC_CLK_SRC_OFF   = 0U, /*!< ADC clock source is OFF */
    ADC_CLK_SRC_PERI1 = 1U, /*!< ADC clock source is PERI1 */
    ADC_CLK_SRC_HSI   = 2U, /*!< ADC clock source is HSI */
    ADC_CLK_SRC_HSE   = 3U  /*!< ADC clock source is HSE */
} adc_clk_src_t;

/*!
 * @brief CAN clock source
 */
#if (CONFIG_CLOCK_NEW_FEATURES == 1U)
typedef enum {
    CAN_CLK_SRC_OFF   = 0U, /*!< CAN clock source is OFF */
    CAN_CLK_SRC_HSE   = 1U, /*!< CAN clock source is HSE */
    CAN_CLK_SRC_APB   = 2U  /*!< CAN clock source is APB */
} can_clk_src_t;
#else
typedef enum {
    CAN_CLK_SRC_OFF   = 0U, /*!< CAN clock source is OFF */
    CAN_CLK_SRC_HSE   = 1U, /*!< CAN clock source is HSE */
    CAN_CLK_SRC_PERI0 = 2U  /*!< CAN clock source is PERI0 */
} can_clk_src_t;
#endif

/*!
 * @brief Timer clock source
 */
#if (CONFIG_CLOCK_NEW_FEATURES == 1U)
typedef enum {
    TIMER_CLK_SRC_OFF     = 0U, /*!< Timer clock source is OFF */
    TIMER_CLK_SRC_TIMERIN = 1U, /*!< Timer clock source is TIMERIN(external pin) */
    TIMER_CLK_SRC_PERI0   = 2U, /*!< Timer clock source is PERI0 */
    TIMER_CLK_SRC_LSI     = 3U  /*!< Timer clock source is LSI */
} timer_clk_src_t;
#else
typedef enum {
    TIMER_CLK_SRC_OFF     = 0U, /*!< Timer clock source is OFF */
    TIMER_CLK_SRC_TIMERIN = 1U, /*!< Timer clock source is TIMERIN(external pin) */
    TIMER_CLK_SRC_APB     = 2U, /*!< Timer clock source is APB */
    TIMER_CLK_SRC_LSI     = 3U  /*!< Timer clock source is LSI */
} timer_clk_src_t;
#endif

/*!
 * @brief CMP clock source
 */
#if (CONFIG_CLOCK_NEW_FEATURES == 1U)
typedef enum {
    CMP_CLK_SRC_OFF   = 0U, /*!< CMP clock source is OFF */
    CMP_CLK_SRC_PERI0 = 1U, /*!< CMP clock source is PERI0 */
    CMP_CLK_SRC_LSI   = 2U  /*!< CMP clock source is LSI */
} cmp_clk_src_t;
#else
typedef enum {
    CMP_CLK_SRC_OFF   = 0U, /*!< CMP clock source is OFF */
    CMP_CLK_SRC_APB   = 1U, /*!< CMP clock source is APB */
    CMP_CLK_SRC_LSI   = 2U  /*!< CMP clock source is LSI */
} cmp_clk_src_t;
#endif

/*!
 * @brief AHB clock prescaler
 */
typedef enum {
    AHB_CLK_PRESCALER_1 = 0U, /*!< AHB clock prescaled by 1 */
    AHB_CLK_PRESCALER_2 = 1U, /*!< AHB clock prescaled by 2 */
    AHB_CLK_PRESCALER_4 = 2U, /*!< AHB clock prescaled by 4 */
    AHB_CLK_PRESCALER_8 = 3U  /*!< AHB clock prescaled by 8 */
} ahb_clk_prescaler_t;

/*!
 * @brief APB clock prescaler
 */
typedef enum {
    APB_CLK_PRESCALER_1 = 0U, /*!< APB clock prescaled by 1 */
    APB_CLK_PRESCALER_2 = 1U, /*!< APB clock prescaled by 2 */
    APB_CLK_PRESCALER_4 = 2U  /*!< APB clock prescaled by 4 */
} apb_clk_prescaler_t;

/*!
 * @brief PERI0 clock prescaler
 */
#if (CONFIG_CLOCK_NEW_FEATURES == 1U)
typedef enum {
    PERI0_CLK_PRESCALER_1 = 0U, /*!< PERI0 clock prescaled by 1 */
    PERI0_CLK_PRESCALER_2 = 1U, /*!< PERI0 clock prescaled by 2 */
    PERI0_CLK_PRESCALER_4 = 2U, /*!< PERI0 clock prescaled by 4 */
} peri0_clk_prescaler_t;
#else
typedef enum {
    PERI0_CLK_PRESCALER_1 = 0U, /*!< PERI0 clock prescaled by 1 */
    PERI0_CLK_PRESCALER_2 = 1U, /*!< PERI0 clock prescaled by 2 */
    PERI0_CLK_PRESCALER_4 = 2U, /*!< PERI0 clock prescaled by 4 */
    PERI0_CLK_PRESCALER_8 = 3U  /*!< PERI0 clock prescaled by 8 */
} peri0_clk_prescaler_t;
#endif

/*!
 * @brief PERI1 clock prescaler
 */
#if (CONFIG_CLOCK_NEW_FEATURES == 1U)
typedef enum {
    PERI1_CLK_PRESCALER_4  = 0U, /*!< PERI1 clock prescaled by 4 */
    PERI1_CLK_PRESCALER_6  = 1U, /*!< PERI1 clock prescaled by 6 */
    PERI1_CLK_PRESCALER_8  = 2U, /*!< PERI1 clock prescaled by 8 */
    PERI1_CLK_PRESCALER_10 = 3U  /*!< PERI1 clock prescaled by 10 */
} peri1_clk_prescaler_t;
#else
typedef enum {
    PERI1_CLK_PRESCALER_1 = 0U, /*!< PERI1 clock prescaled by 1 */
    PERI1_CLK_PRESCALER_2 = 1U, /*!< PERI1 clock prescaled by 2 */
    PERI1_CLK_PRESCALER_4 = 2U, /*!< PERI1 clock prescaled by 4 */
    PERI1_CLK_PRESCALER_8 = 3U  /*!< PERI1 clock prescaled by 8 */
} peri1_clk_prescaler_t;
#endif

/*!
 * @brief Reset state
 */
typedef enum {
    RESET_ACTIVE   = 0U, /*!< Reset is active */
    RESET_INACTIVE = 1U  /*!< Reset is in-active */
} reset_state_t;

/*!
 * @brief HSE/XOSC Loss of Clock generates events
 */
typedef enum {
    LOC_GEN_RESET = 0U, /*!< Loss of clock generates system reset */
    LOC_GEN_INT   = 1U  /*!< Loss of clock generates interrupt */
} loc_event_t;

/*!
 * @brief PLL Loss of Lock generates events
 */
typedef enum {
    LOL_GEN_RESET = 0U, /*!< Loss of lock generates system reset */
    LOL_GEN_INT   = 1U  /*!< Loss of lock generates interrupt */
} lol_event_t;

#if (CONFIG_CLOCK_NEW_FEATURES == 0U)
/*!
 * @brief PLL VCO Reference
 */
typedef enum {
    VCO_REF_0   = 0U, /*!< VCO reference 0 */
    VCO_REF_1   = 1U, /*!< VCO reference 1 */
    VCO_REF_MAX = 2U  /*!< Max */
} vco_ref_t;
#endif

/*!
 * @brief PLL VCO frequency
 */
#if (CONFIG_CLOCK_NEW_FEATURES == 1U)
typedef enum {
    VCO_FREQ_960M = 0U, /*!< VCO = 960MHz */
    VCO_FREQ_MAX  = 1U  /*!< Max */
} vco_freq_t;
#else
typedef enum {
    VCO_FREQ_672M = 0U, /*!< VCO = 672MHz */
    VCO_FREQ_576M = 1U, /*!< VCO = 576MHz */
    VCO_FREQ_MAX  = 2U  /*!< Max */
} vco_freq_t;
#endif

/*!
 * @brief PLL input reference frequency
 */
typedef enum {
    PLL_IN_4M  = 0U, /*!< Input: 4MHz */
    PLL_IN_8M  = 1U, /*!< Input: 8MHz */
    PLL_IN_12M = 2U, /*!< Input: 12MHz */
    PLL_IN_16M = 3U, /*!< Input: 16MHz */
    PLL_IN_20M = 4U, /*!< Input: 20MHz */
    PLL_IN_24M = 5U, /*!< Input: 24MHz */
    PLL_IN_30M = 6U, /*!< Input: 30MHz */
    PLL_IN_MAX = 7U  /*!< Max */
} pll_in_t;

/*!
 * @brief PLL output frequency
 */
#if (CONFIG_CLOCK_NEW_FEATURES == 1U)
typedef enum {
    PLL_OUT_80M  = 0U, /*!< Output: 80MHz */
    PLL_OUT_120M = 1U, /*!< Output: 120MHz */
    PLL_OUT_MAX  = 2U  /*!< Max */
} pll_out_t;
#else
typedef enum {
    PLL_OUT_24M = 0U, /*!< Output: 24MHz */
    PLL_OUT_48M = 1U, /*!< Output: 48MHz */
    PLL_OUT_MAX = 2U  /*!< Max */
} pll_out_t;
#endif

/*!
 * @brief PLL configurations
 *
 *        fclk_out = fclk_in * (fbdiv + 5) / ((prediv + 1) * (postdiv1 + 1) * (2 ^ postdiv2))
 *        fclk_in is PLL's reference clock, fclk_out is PLL's output clock
 */
typedef struct {
    lol_event_t lolEvent;        /*!< Event that loss of lock will generates */
    pll_ref_clk_src_t pllRefClk; /*!< PLL reference clock source */
    uint16_t prediv;             /*!< PLL previous divider, 0~63 */
    uint16_t fbdiv;              /*!< PLL feedback divider, 0~4095 */
    uint16_t postdiv1;           /*!< PLL post divider1, 0~7 */
    uint16_t postdiv2;           /*!< PLL post divider2, 0~3 */
    bool lockMonEnable;          /*!< PLL lock monitor enable */
    bool enable;                 /*!< PLL enable */
} pll_config_t;

/*!
 * @brief HSI configurations
 */
typedef struct {
    bool enableInStop;  /*!< HSI will keep alive in stop mode if set (default),
                             or else it will be turned off automatically by hardware */
} hsi_config_t;

/*!
 * @brief HSE configurations
 */
typedef struct {
    uint32_t freq;        /*!< Frequency of external crystal or oscillator, unit is Hz */

    loc_event_t locEvent; /*!< Event that loss of clock will generates */
    bool monEnable;       /*!< HSE monitor enable */
    bool enable;          /*!< HSE enable: true-enable, false-bypass */
} hse_config_t;

/*!
 * @brief Clock configurations
 */
typedef struct {
    /* System clock sources configuration */
    hsi_config_t hsiConfig; /*!< HSI configuration */
    hse_config_t hseConfig; /*!< HSE configuration */
    pll_config_t pllConfig; /*!< PLL configuration */

    /* Clock prescalers */
    ahb_clk_prescaler_t ahbClkPrescaler;     /*!< AHB clock prescaler */
    apb_clk_prescaler_t apbClkPrescaler;     /*!< APB clock prescaler (Bus clock) */
    peri0_clk_prescaler_t peri0ClkPrescaler; /*!< Peripheral0 clock prescaler */
    peri1_clk_prescaler_t peri1ClkPrescaler; /*!< Peripheral1 clock prescaler */

    /* Module clock source select */
    sys_clk_src_t sysClkSrc;                                  /*!< System clock source */
    adc_clk_src_t adcClkSrc[CONFIG_ADC_INSTANCE_COUNT];       /*!< ADC clock source */
    can_clk_src_t canClkSrc[CONFIG_CAN_INSTANCE_COUNT];       /*!< CAN clock source */
    timer_clk_src_t timerClkSrc[CONFIG_TIMER_INSTANCE_COUNT]; /*!< Timer clock source */
    cmp_clk_src_t cmpClkSrc[CONFIG_CMP_INSTANCE_COUNT];       /*!< CMP clock source */
} clock_config_t;

/*******APIs*******************************************************************/
#if defined(__cplusplus)
extern "C" {
#endif

/*!
 * @brief Get a default predefined clock configurations
 *
 * @param[out] config: Pointer to clock configuration strcucture clock_config_t
 * @param[in] pllIn: PLL input reference frequency
 *                   It must be configured as the actual frequency of reference crystal
 *                   or oscillator if PLL is used as system clock, or it can be any
 *                   value if HSI or HSE is used as system clock
 * @param[in] pllOut: PLL output frequency
 *                    It must be configured as the expected output frequency of PLL if
 *                    PLL is used as system clock, or it can be any value if HSI or HSE
 *                    is used as system clock
 * @return None
 */
void CLOCK_GetDefaultConfig(clock_config_t* config, pll_in_t pllIn, pll_out_t pllOut);

/*!
 * @brief Get current clock configurations
 *        User can use this function to store current clock configurations before enter Stop mode,
 *        and restore them after wakeup.
 *
 * @param[out] config: Pointer to clock configuration strcucture clock_config_t
 * @return None
 */
void CLOCK_GetCurrentConfig(clock_config_t* config);

/*!
 * @brief Initial clock settigs according to pre-defined structure
 *
 * @param[in] config: Pointer to clock configuration strcucture clock_config_t
 * @return ERR_SUCCESS: Initial success
 *         ERR_ERROR: Initial error because of XOSC or PLL fail
 */
errcode_t CLOCK_Init(const clock_config_t* config);

/*!
 * @brief Deinit clock and restore settings
 *
 * @return None
 */
void CLOCK_Deinit(void);

/*!
 * @brief Get the frequency of a certain clock by its name
 *
 * @param[in] clockName: Clock names defined in clk_names_t
 * @param[out] freq: Returned frequency value, unit is Hz
 * @return ERR_SUCCESS: Get frequency success
 *         ERR_ERROR: Get frequency fail
 */
errcode_t CLOCK_GetFreq(clk_names_t clockName, uint32_t* freq);

/*!
 * @brief Configure settings for system clock sources: HSI, HSE, PLL
 *
 * @param[in] sysClkSrc: Clocks for system clock, one of CLK_HSI, CLK_HSE, CLK_PLL
 * @param[in] sysClkConfig: Pointer to clock configuration structure
 *                          For CLK_HSI, the structure should be hsi_config_t
 *                          For CLK_HSE, the structure should be hse_config_t
 *                          For CLK_PLL, the structure should be pll_config_t
 * @return ERR_SUCCESS: Configure success
 *         ERR_UNSUPPORTED: sysClkSrc is not one of CLK_HSI, CLK_HSE, CLK_PLL
 *         ERR_TIMEOUT: Timeout for CLK_HSE or CLK_PLL
 */
errcode_t CLOCK_SetSystemClockSource(clk_names_t sysClkSrc, const void* clkConfig);

/*!
 * @brief Set if HSI clock keep enabled in STOP mode
 *
 * @param[in] enable: true-enabled, false-disabled
 * @return None
 */
void CLOCK_SetHsiEnableInStop(bool enable);

/*!
 * @brief Configure prescaler value for main clocks: AHB, APB, PERI
 *
 * @param[in] clkName: Main clock names: CLK_AHB, CLK_APB, CLK_PERI, defined in clk_names_t
 * @param[in] prescaler: Prescaler value for main clocks
 *                       For CLK_AHB, refer to enum ahb_clk_prescaler_t
 *                       For CLK_APB, refer to enum apb_clk_prescaler_t
 *                       For CLK_PERI0, refer to enum peri0_clk_prescaler_t
 *                       For CLK_PERI1, refer to enum peri1_clk_prescaler_t
 * @return ERR_SUCCESS: Set main clock prescaler success
 *         ERR_UNSUPPORTED: clkName is not one of CLK_AHB, CLK_APB, CLK_PERI
 */
errcode_t CLOCK_SetMainClockPrescaler(clk_names_t clkName, uint16_t prescaler);

/*!
 * @brief Select clock source for system clock
 *
 * @param[in] sysClock: System clock name: CLK_SYS, defined in clk_names_t
 * @param[in] sysClkSrc: System clock source defined in enum sys_clk_src_t
 * @return None
 */
void CLOCK_SelectSystemClockSource(clk_names_t sysClock, sys_clk_src_t sysClkSrc);

/*!
 * @brief Select clock source for module clocks
 *
 * @param[in] moduleClock: Clock names of modules whcih can be configured clock source:
 *                         CLK_ADC0, CLK_CAN0, CLK_TIMER0, CLK_TIMER1, CLK_CMP0,
 *                         all defined in clk_names_t
 * @param[in] moduleClkSrc: Module clock source
 *                          For CLK_ADC0, refer to enum adc_clk_src_t
 *                          For CLK_CAN0, refer to enum can_clk_src_t
 *                          For CLK_TIMER0 and CLK_TIMER1, refer to enum timer_clk_src_t
 *                          For CLK_CMP0, refer to enum cmp_clk_src_t
 * @return ERR_SUCCESS: Select module clock source success
 *         ERR_UNSUPPORTED: moduleClock is not one of above clocks
 */
errcode_t CLOCK_SelectModuleClockSource(clk_names_t moduleClock, uint16_t moduleClkSrc);

/*!
 * @brief Enable or disable bus clock for modules
 *
 * @param[in] moduleName: Module names defined in module_names_t
 * @param[in] enable: true: bus clock enable
 *                    false: bus clock disable
 * @return None
 */
void CLOCK_SetModuleBusClockEnable(module_names_t moduleName, bool enable);

/*!
 * @brief Set soft reset state (active or inactive) for modules
 *
 * @param[in] moduleName: Module names defined in module_names_t
 * @param[in] resetState: RESET_ACTIVE: soft reset is active
 *                        RESET_INACTIVE: soft reset is inactive
 * @return None
 */
void CLOCK_SetModuleSoftResetState(module_names_t moduleName, reset_state_t resetState);

/*!
 * @brief Clear HSE loss of clock flag
 *
 * @return None
 */
void CLOCK_ClearLocFlag(void);

/*!
 * @brief Clear Pll loss of lock flag
 *
 * @return None
 */
void CLOCK_ClearLolFlag(void);

#if defined(__cplusplus)
}
#endif

/*! @} */

#endif /* CLOCK_DRV_H */

/*******EOF********************************************************************/
